package com.oschina.sequence;

import com.oschina.sequence.exception.SequenceException;
import com.oschina.sequence.exception.SequenceNoFreeConnectionException;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * Created by limu on 16/6/28.
 */
public class SequenceManager {
    /**
     * 空闲sequenceTable
     */
    private BlockingQueue<SequenceTable> sequenceTableQueue;

    /**
     * 等待超时时间，默认值500ms
     */
    private long timeout = 500;

    /**
     * id名称
     */
    private String idName;

    /**
     * 应用名称
     */
    private String appName;

    /**
     * 设置sequence table个数
     */
    private int tableSize = 2;

    /**
     * 增长步长，步长越大可支持的并发量越高
     */
    private int step = 100;

    /**
     * id初始值
     */
    private long initValue = 1000000;

    /**
     * 数据库信息
     */
    private List<DatabaseConfig> databaseConfigs;

    /**
     * 初始化
     *
     * @throws SequenceException
     */
    public void init() throws SequenceException {
        if (null == databaseConfigs || databaseConfigs.isEmpty()) {
            throw new SequenceException("必须设置databaseConfigs");
        }
        if (null == idName) {
            throw new SequenceException("必须设置产生idName");
        }
        if (null == appName) {
            throw new SequenceException("必须设置appName");
        }

        //初始化sequenceTableQueue
        sequenceTableQueue = new ArrayBlockingQueue<>(databaseConfigs.size() * tableSize);

        //初始化数据库连接池
        DbPoolManger.init(databaseConfigs);

        for (int dbIndex = 0; dbIndex < databaseConfigs.size(); dbIndex++) {
            for (int tableIndex = 0; tableIndex < tableSize; tableIndex++) {
                if (!checkTableExist(dbIndex, tableIndex)) {
                    throw new SequenceException("table sequence" + tableIndex + " must be created in database(" + databaseConfigs.get(dbIndex).getHost() + ")");
                }

                String sequenceName = appName + "_" + idName;
                SequenceTable sequenceTable = new SequenceTable(databaseConfigs.size(), dbIndex, tableSize, tableIndex, step, initValue, sequenceName);
                sequenceTable.init();
                sequenceTableQueue.add(sequenceTable);
            }
        }
    }

    /**
     * 销毁
     *
     * @throws SequenceException
     */
    public void destroy() throws SequenceException {
        DbPoolManger.destroy();
    }

    /**
     * 获取nextValue值,如果获取失败，尝试再次获取
     */
    public long nextValue() throws SequenceException {
        try {
            return getNextValue();
        } catch (SequenceNoFreeConnectionException e) {
            //由于没有可用连接导致的，此种情况无需再次获取nextValue，直接返回异常
            throw e;
        } catch (SequenceException e) {
            // 如果是数据库连接导致的异常，则尝试再次获取

            try {
                return getNextValue();
            } catch (SequenceException e1) {
                throw e1;
            }
        }
    }

    public void setIdName(String idName) {
        this.idName = idName;
    }

    public void setAppName(String appName) {
        this.appName = appName;
    }

    public void setDatabaseConfigs(List<DatabaseConfig> databaseConfigs) {
        this.databaseConfigs = databaseConfigs;
    }

    public void setTableSize(int tableSize) {
        this.tableSize = tableSize;
    }

    public void setStep(int step) {
        this.step = step;
    }

    public void setInitValue(long initValue) {
        this.initValue = initValue;
    }

    public String getIdName() {
        return idName;
    }

    public String getAppName() {
        return appName;
    }

    /**
     * 获取nextValue值
     *
     * @return
     * @throws SequenceException
     */
    private long getNextValue() throws SequenceException {
        // 获取连接
        SequenceTable sequenceTable = pollFreeConnectionQueue();
        if (null == sequenceTable) {
            throw new SequenceNoFreeConnectionException("there is no free connection! idName=" + idName);
        }

        //获取值
        long nextValue = sequenceTable.nextValue();

        //释放连接
        putFreeConnectionQueue(sequenceTable);

        return nextValue;

    }

    private SequenceTable pollFreeConnectionQueue() throws SequenceException {
        try {
            return sequenceTableQueue.poll(timeout, TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            throw new SequenceException("freeConnectionQueue.poll!", e);
        }
    }

    private void putFreeConnectionQueue(SequenceTable sequenceTable) throws SequenceException {
        try {
            sequenceTableQueue.put(sequenceTable);
        } catch (Exception e) {
            throw new SequenceException("putFreeConnectionQueue.put error!", e);
        }
    }

    private boolean checkTableExist(int dbIndex, int tableIndex) throws SequenceException {
        Statement statement = null;
        ResultSet queryResult = null;
        Connection connection = DbPoolManger.getConnection(dbIndex);
        String tableName = "sequence" + tableIndex;

        try {
            //step1 设置自动提交模式，无需commit
            connection.setAutoCommit(true);

            //step3 查询表
            String sql = "show tables like '" + tableName + "'";
            statement = connection.createStatement();
            queryResult = statement.executeQuery(sql);

            //step3 解析sql查询
            String tempTableName = null;
            if (queryResult.next()) {
                tempTableName = queryResult.getString(1);
            }
            if (null == tempTableName || !tempTableName.equals(tableName)) {
                return false;
            }

            return true;
        } catch (Exception e) {
            throw new SequenceException(e);
        } finally {
            // 关闭资源
            try {
                if (null != queryResult) {
                    queryResult.close();
                    queryResult = null;
                }

                if (null != statement) {
                    statement.close();
                    statement = null;
                }

                if (null != connection) {
                    connection.close();
                }
            } catch (Exception e) {
                throw new SequenceException(toString(), e);
            }
        }
    }

}
